﻿using System;
using System.Linq;
using ReactiveGraph.Core;
using ReactiveGraph.Core.Extensions;

namespace ReactiveGraph.Object
{
    public class AggregateProperty<T> : IProperty
    {
        private IReactiveNode _parent;
        private IObserver<NodeEvent> _changed;
        private readonly string _name;
        private readonly Func<T, T, T> _accumulator;
        private IObservable<T> _aggregateChanged;

        public AggregateProperty(string name, Func<T,T,T> accumulator)
        {
            _name = name;
            _accumulator = accumulator;
        }

        void IProperty.Initialize(IReactiveNode parent, IObserver<NodeEvent> changed)
        {
            _parent = parent;
            _changed = changed;
            _aggregateChanged = AggregateChanged();
            _parent.AttachBehaviour(_aggregateChanged.ToUnit());
        }

        private IObservable<T> AggregateChanged()
        {
            return _parent.Events
                .Where(e => IsMatch(e.Source.Property))
                .Select(e => GetCurrentValue())
                .DistinctUntilChanged().Skip(1)
                .Do(value=> _changed.OnNext(NodeEvent.PropertyChanged(_parent, this, value)));
        }

        private T GetCurrentValue()
        {
            return _parent.GetDescendantProperties()
                .Where(IsMatch)
                .Select(property => property.Value).Cast<T>()
                .Aggregate(_accumulator);
        }


        string IProperty.Name
        {
            get { return _name; }
        }

        public T Value
        {
            get
            {
                return GetCurrentValue();
            }
        }

        private bool IsMatch(IProperty property)
        {
            return property != this &&
                   property.Name == _name &&
                   property.Type == typeof (T);
        }

        Type IProperty.Type
        {
            get { return typeof (T); }
        }

        object IProperty.Value
        {
            get { return Value; }
            set
            {
                throw new NotImplementedException();
            }
        }
    }
}